﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Pipes.Queries.Model.Query;

namespace Pipes.Queries.Provider.SqlServer
{
  public class SqlServerWriter : TextWriter, Parser.IQueryVisitor
  {
    private bool _firstJoin = true;
    private TextWriter _writer;
    private bool _writeStringValue = false;

    public override Encoding Encoding
    {
      get { return _writer.Encoding; }
    }

    public SqlServerWriter() : this(new StringWriter()) { }
    public SqlServerWriter(TextWriter writer)
    {
      _writer = writer;
    }

    public override string ToString()
    {
      return _writer.ToString();
    }

    public void Visit(Model.Query.Conditional value)
    {
      _writer.Write("CASE ");
      if (value.Root != null)
      {
        value.Root.Visit(this);
        _writer.Write(" ");
      }
      foreach (var branch in value.Branches) branch.Visit(this);
      if (value.Else != null)
      {
        _writer.Write(" ELSE ");
        value.Else.Visit(this);
      }
      _writer.Write(" END");
    }

    public void Visit(Model.Query.ConditionBranch value)
    {
      _writer.Write("WHEN ");
      value.Condition.Visit(this);
      _writer.Write(" THEN ");
      value.Branch.Visit(this);
    }

    public void Visit(Model.Query.Constant value)
    {
      switch (value.Value)
      {
        case Model.Query.ConstantType.@null:
          _writer.Write("NULL");
          break;
        case Model.Query.ConstantType.sysdate:
          _writer.Write("SYSDATETIME()");
          break;
        default:
          throw new NotImplementedException();
      }
    }

    public void Visit(Model.Query.DateLiteral value)
    {
      _writer.Write("'");
      _writer.Write("{0:s}", value.Value);
      _writer.Write("'");
    }

    public void Visit(Model.Query.Function value)
    {
      value.Name.Visit(this);
      _writer.Write('(');
      for (var i = 0; i < value.Args.Count; i++)
      {
        if (i > 0) _writer.Write(", ");
        value.Args[i].Visit(this);
      }
      _writer.Write(')');
    }

    public void Visit(Model.Query.Identifier value)
    {
      bool first = true;
      foreach(var part in value.Parts)
      {
        if (!first) _writer.Write(".");
        first = false;
        _writer.Write("[");
        _writer.Write(part);
        _writer.Write("]");
      }
    }

    public void Visit(Model.Query.Join value)
    {
      if (!_firstJoin)
      {
        switch(value.Type)
        {
          case Model.Query.JoinType.Cross:
            _writer.Write("CROSS JOIN ");
            break;
          case Model.Query.JoinType.FullOuter:
            if (value.Natural) _writer.Write("NATURAL ");
            _writer.Write("FULL JOIN ");
            break;
          case Model.Query.JoinType.LeftOuter:
            if (value.Natural) _writer.Write("NATURAL ");
            _writer.Write("LEFT JOIN ");
            break;
          case Model.Query.JoinType.RightOuter:
            if (value.Natural) _writer.Write("NATURAL ");
            _writer.Write("RIGHT JOIN ");
            break;
          default: //Inner
            _writer.Write("INNER JOIN ");
            break;
        }
      }

      Visit((SimpleDeclaration)value);

      if (!_firstJoin)
      {
        _writer.Write(" ON ");
        value.Criteria.Visit(this);
      }
      _firstJoin = false;
    }

    public void Visit(Model.Query.LogicalNot value)
    {
      _writer.Write("NOT ");
      value.Operand.Visit(this);
    }

    public void Visit(Model.Query.NumericLiteral value)
    {
      _writer.Write(value.Value);
    }

    // Need to deal with operator precedence
    public void Visit(Model.Query.Operation value)
    {
      if (value.Type == Operator.Power) _writer.Write("POWER(");

      string op = string.Empty;
      switch (value.Type)
      {
        case Operator.Ambig_BitwiseAndConcat:
        case Operator.BitwiseAnd:
          op = "&";
          break;
        case Operator.Addition:
        case Operator.Ambig_AdditionConcat:
          op = "+";
          break;
        case Operator.Ambig_LogicalOrConcat:
        case Operator.LogicalOr:
          op = "OR";
          break;
        case Operator.BitwiseOr:
          op = "|";
          break;
        case Operator.BitwiseXor:
          op = "^";
          break;
        case Operator.Concatenation:
          op = "+";
          break;
        case Operator.Division:
          op = "/";
          break;
        case Operator.Equals:
          op = "=";
          break;
        case Operator.GreaterEqual:
          op = ">=";
          break;
        case Operator.GreaterThan:
          op = ">";
          break;
        case Operator.In:
          op = "IN";
          break;
        case Operator.Is:
          op = "IS";
          break;
        case Operator.LessEqual:
          op = "<=";
          break;
        case Operator.LessThan:
          op = "<";
          break;
        case Operator.PatternMatch:
          op = "LIKE";
          break;
        case Operator.LogicalAnd:
          op = "AND";
          break;
        case Operator.Modulus:
          op = "%";
          break;
        case Operator.Multiplication:
          op = "*";
          break;
        case Operator.NotEqual:
          op = "<>";
          break;
        case Operator.Power:
          op = ",";
          break;
        case Operator.Negative:
        case Operator.Subtraction:
          op = "-";
          break;
        case Operator.BitwiseNot:
          op = "^";
          break;
        default:
          throw new ArgumentException(value.Type.ToString() + " is not supported");
      }

      for (var i = 0; i < value.Args.Count; i++)
      {
        if (i > 0)
        {
          _writer.Write(" ");
          _writer.Write(op);
          _writer.Write(" ");
        }
        value.Args[i].Visit(this);
      }

      if (value.Type == Operator.Power) _writer.Write(")");
    }

    public void Visit(Model.Query.OrderSpec value)
    {
      value.Expr.Visit(this);
      if (value.Direction == OrderDirection.Descending) _writer.Write(" DESC");
    }

    public void Visit(Model.Pattern.PatternOpts value)
    {
      try
      {
        StringValue();
        var patternWriter = new SqlPatternWriter(this, Parser.PatternMatchDefinition.SqlServerDefn);
        value.Visit(patternWriter);
      }
      finally
      {
        StringValueEnd();
      }
    }

    public void Visit(Model.Query.ReadBlock value)
    {
      for (var i = 0; i < value.ReadStatements.Count; i++)
      {
        if (i > 0) _writer.WriteLine();
        value.ReadStatements[i].Visit(this);
      }
    }

    public void Visit(Model.Query.ReadStatement value)
    {
      _writer.Write("SELECT ");

      for (var i = 0; i < value.Columns.Count; i++)
      {
        if (i > 0) _writer.Write(", ");
        value.Columns[i].Visit(this);
      }
      _firstJoin = true;
      for (var i = 0; i < value.Source.Count; i++)
      {
        if (i == 0) _writer.Write(" FROM ");
        value.Source[i].Visit(this);
      }
      if (value.Where != null)
      {
        _writer.Write(" WHERE ");
        value.Where.Visit(this);
      }
      for (var i = 0; i < value.GroupBy.Count; i++)
      {
        if (i == 0)
        {
          _writer.Write(" GROUP BY ");
        }
        else
        {
          _writer.Write(", ");
        }
        value.GroupBy[i].Visit(this);
      }
      if (value.Having != null)
      {
        _writer.Write(" HAVING ");
        value.Having.Visit(this);
      }
      for (var i = 0; i < value.OrderBy.Count; i++)
      {
        if (i == 0)
        {
          _writer.Write(" ORDER BY ");
        }
        else
        {
          _writer.Write(", ");
        }
        value.OrderBy[i].Visit(this);
      }
      _writer.Write(";");
    }

    public void Visit(Model.Query.Script value)
    {
      for (var i = 0; i < value.Statements.Count; i++)
      {
        if (i > 0) _writer.WriteLine();
        value.Statements[i].Visit(this);
      }
    }

    public void Visit(Model.Query.SimpleDeclaration value)
    {
      value.Expr.Visit(this);
      if (value.Alias != null && !string.IsNullOrEmpty(value.Alias.ToString()))
      {
        _writer.Write(" AS ");
        value.Alias.Visit(this);
      }
    }

    public void Visit(Model.Query.StringLiteral value)
    {
      try
      {
        StringValue();
        Write(value.ToString());
      }
      finally
      {
        StringValueEnd();
      }
    }

    public void Visit(Model.Query.UpdateStatement value)
    {
      _writer.Write("UPDATE ");
      value.Source.Visit(this);
      _writer.Write(" SET ");
      for (var i = 0; i < value.Assignments.Count; i++)
      {
        if (i > 0) _writer.Write(", ");
        value.Assignments[i].Visit(this);
      }
      if (value.Where != null)
      {
        _writer.Write(" WHERE ");
        value.Where.Visit(this);
      }
    }

    public override void Write(char value)
    {
      Write(value.ToString());
    }
    public override void Write(char[] buffer, int index, int count)
    {
      Write(new String(buffer, index, count));
    }
    public override void Write(string value)
    {
      if (_writeStringValue)
      {
        _writer.Write(value.Replace("'", "''"));
      }
      else
      {
        _writer.Write(value);
      }
    }

    private void StringValue()
    {
      Write('\'');
      _writeStringValue = true;
    }

    private void StringValueEnd()
    {
      _writeStringValue = false;
      Write('\'');
    }
  }
}
